/*
 * Copyright (c) 2023 Hong Jiahua
 * https://gitee.com/arrco/easypack
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
#include <stdio.h>
#include <string.h>
#include <stdint.h>

#include "easypack.h"
/************************************************************************/
/*                                                                      */
/************************************************************************/
typedef struct {
    char name[EASYPACK_NAME_LEN];
    char size[4];
    char type;
    char pad[EASYPACK_PAD_LEN];
} easypack_raw_header_t;

/************************************************************************/
/*                                                                      */
/************************************************************************/
static inline int file_write(easypack_t *obj, const void *data, unsigned int size) {
    return fwrite(data, 1, size, obj->fp);
}

static inline int file_read(easypack_t *obj, void *data, unsigned int size) {
    return fread(data, 1, size, obj->fp);
}

static inline int file_seek(easypack_t *obj, unsigned int offset) {
    return fseek(obj->fp, offset, SEEK_SET);
}

static inline void* file_open(easypack_t *obj, const char *filename, const char *mode) {
    return fopen(filename, mode);
}

static inline void file_close(easypack_t *obj) {
    fclose(obj->fp);
}

/************************************************************************/
/*                                                                      */
/************************************************************************/
static int easypack_read(easypack_t *obj, void *data, unsigned int size) {
    unsigned int res = file_read(obj, data, size);
    obj->pos += res;
    return (res == size) ? EASYPACK_SUCCESS : EASYPACK_FAILURE;
}

static int easypack_write(easypack_t *obj, const void *data, unsigned int size) {
    unsigned int res = file_write(obj, data, size);
    obj->pos += res;
    return (res == size) ? EASYPACK_SUCCESS : EASYPACK_FAILURE;
}

static int easypack_seek(easypack_t *obj, unsigned int pos) {
    int res = file_seek(obj, pos);
    obj->pos = pos;
    return (res == 0) ? EASYPACK_SUCCESS : EASYPACK_FAILURE;
}

/************************************************************************/
/*                                                                      */
/************************************************************************/
/**
  * @brief      打开包文件
  * @param[in]  obj         对象
  * @param[in]  filename    包文件名称
  * @param[in]  mode        模式
  *
  * @return     errcode
  * @retval      0      成功
  * @retval     -1      失败
  */
int easypack_open(easypack_t *obj, const char *filename, const char *mode) {
    memset(obj, 0, sizeof(easypack_t));
    
    if (strchr(mode, 'r')) {
        mode = "rb";
    } else if (strchr(mode, 'w')) {
        mode = "wb";
    } else {
        return EASYPACK_FAILURE;
    }
    
    obj->fp = file_open(obj, filename, mode);
    return (obj->fp) ? EASYPACK_SUCCESS : EASYPACK_FAILURE;
}

/**
  * @brief      关闭包文件
  * @param[in]  obj     对象
  *
  * @return     errcode
  * @retval      0      成功
  * @retval     -1      失败
  */
int easypack_close(easypack_t *obj) {
    file_close(obj);
    return EASYPACK_SUCCESS;
}

/**
  * @brief      读取文件的头信息
  * @param[in]  obj     对象
  * @param[in]  header  文件信息
  *
  * @return     errcode
  * @retval      0      成功
  * @retval     -1      失败
  */
int easypack_read_header(easypack_t *obj, easypack_header_t *header) {
    int err;
    easypack_raw_header_t rh;
    
    obj->last_header = obj->pos;
    
    err = easypack_read(obj, &rh, sizeof(rh));
    if (err) {
        return err;
    }
    
    err = easypack_seek(obj, obj->last_header);
    if (err) {
        return err;
    }
    
    strcpy(header->name, rh.name);
    header->size = *(uint32_t *)(rh.size);
    header->type = rh.type;
    return EASYPACK_SUCCESS;
}

/**
  * @brief      读取文件数据
  * @param[in]  obj     对象
  * @param[in]  data    数据
  * @param[in]  size    长度
  *
  * @return     errcode
  * @retval      0      成功
  * @retval     -1      失败
  */
int easypack_read_data(easypack_t *obj, void *data, unsigned int size) {
    int err;
    
    if (obj->remaining_data == 0) {
        easypack_header_t h;
        err = easypack_read_header(obj, &h);
        if (err) {
            return err;
        }
        
        err = easypack_seek(obj, obj->pos + sizeof(easypack_raw_header_t));
        if (err) {
            return err;
        }
        obj->remaining_data = h.size;
    }
    size = size > obj->remaining_data ? obj->remaining_data : size;
    
    err = easypack_read(obj, data, size);
    if (err) {
        return err;
    }
    
    obj->remaining_data -= size;
    if (obj->remaining_data == 0) {
        return easypack_seek(obj, obj->last_header);
    }
    return EASYPACK_SUCCESS;
}

/**
  * @brief      定位到起始位置
  * @param[in]  obj     对象
  *
  * @return     errcode
  * @retval      0      成功
  * @retval     -1      失败
  */
int easypack_rewind(easypack_t *obj) {
    obj->remaining_data = 0;
    obj->last_header = 0;
    return easypack_seek(obj, 0);
}

/**
  * @brief      定位到下一个文件头位置
  * @param[in]  obj     对象
  *
  * @return     errcode
  * @retval      0      成功
  * @retval     -1      失败
  */
int easypack_next(easypack_t *obj) {
    int err;
    easypack_header_t h;
    
    err = easypack_read_header(obj, &h);
    if (err) {
        return err;
    }
    
    return easypack_seek(obj, obj->pos + h.size + sizeof(easypack_raw_header_t));
}

/**
  * @brief      查找文件
  * @param[in]  obj         对象
  * @param[in]  filename    名称
  * @param[in]  header      文件信息
  *
  * @return     errcode
  * @retval      0      成功
  * @retval     -1      失败
  */
int easypack_find(easypack_t *obj, const char *filename, easypack_header_t *header) {
    int err;
    easypack_header_t cur_header;
    
    err = easypack_rewind(obj);
    if (err) {
        return err;
    }
    
    while ( easypack_read_header(obj, &cur_header) == EASYPACK_SUCCESS ) {
        if ( !strcmp(cur_header.name, filename) ) {
            if (header) {
                *header = cur_header;
            }
            return EASYPACK_SUCCESS;
        }
        easypack_next(obj);
    }
    return EASYPACK_FAILURE;
}

static int easypack_write_header(easypack_t *obj, const char *name, unsigned int size, char type) {
    easypack_raw_header_t rh = {0};
    strcpy(rh.name, name);
    *(uint32_t *)(rh.size) = size;
    rh.type = type;
    return easypack_write(obj, &rh, sizeof(rh));
}

/**
  * @brief      写入文件的头信息
  * @param[in]  obj         对象
  * @param[in]  filename    名称
  * @param[in]  size        文件大小
  *
  * @return     errcode
  * @retval      0      成功
  * @retval     -1      失败
  */
int easypack_write_file_header(easypack_t *obj, const char *filename, unsigned int size) {
    return easypack_write_header(obj, filename, size, EASYPACK_FILE);
}

/**
  * @brief      写入文件夹的头信息
  * @param[in]  obj     对象
  * @param[in]  dirname 名称
  *
  * @return     errcode
  * @retval      0      成功
  * @retval     -1      失败
  */
int easypack_write_dir_header(easypack_t *obj, const char *dirname) {
    return easypack_write_header(obj, dirname, 0, EASYPACK_DIR);
}

/**
  * @brief      写入文件数据
  * @param[in]  obj     对象
  * @param[in]  data    数据
  * @param[in]  size    长度
  *
  * @return     errcode
  * @retval      0      成功
  * @retval     -1      失败
  */
int easypack_write_data(easypack_t *obj, const void *data, unsigned int size) {
    return easypack_write(obj, data, size);
}
